--! This file is part of the FELIX firmware distribution (https://gitlab.cern.ch/atlas-tdaq-felix/firmware/).
--! Copyright (C) 2001-2021 CERN for the benefit of the ATLAS collaboration.
--! Authors:
--!               Frans Schreuder
--!
--!   Licensed under the Apache License, Version 2.0 (the "License");
--!   you may not use this file except in compliance with the License.
--!   You may obtain a copy of the License at
--!
--!       http://www.apache.org/licenses/LICENSE-2.0
--!
--!   Unless required by applicable law or agreed to in writing, software
--!   distributed under the License is distributed on an "AS IS" BASIS,
--!   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--!   See the License for the specific language governing permissions and
--!   limitations under the License.


library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;

    use IEEE.NUMERIC_STD.ALL;
    use IEEE.NUMERIC_STD_UNSIGNED.ALL;

entity vsec_null is
    generic(
        -- PCIe Extended Capabilty parameters
        -- This parameter should be the offset of this capability.
        -- The first extended capability should be at offset 12'h400
        EXT_CONFIG_BASE_ADDRESS : std_logic_vector(11 downto 0) := x"480"; -- @suppress "Unused generic: EXT_CONFIG_BASE_ADDRESS is not used in work.vsec_null(Behavioral)"
        -- This parameter is the byte-lenth of the PCIe extended capability
        -- and should include the lenght of the header registers
        EXT_CONFIG_CAP_LENGTH  : std_logic_vector(11 downto 0) := x"010";
        -- This parameter should be 12'h000 to terminate the capability chain or
        -- the address of the next capability.
        EXT_CONFIG_NEXT_CAP  : std_logic_vector(11 downto 0) := x"000";
        -- fields for the PCIE_VSEC_ID and PCIE_VSEC_REV_ID are defined
        -- by the vendor and should be qualified by the PCIe-Vendor-ID (Xilinx = 0x10EE),
        -- PCIe-Device-ID, PCIe-Revision-ID, PCIe-Subsystem-Vendor-ID, PCIe-Subsystem-ID,
        -- PCIE-Extended Capability-ID (0x000b), and PCIe-Extended-Capability-Revision-ID
        -- (0x0) prior to interpretation.
        PCIE_VSEC_ID  : std_logic_vector(15 downto 0) := x"0000";
        PCIE_VSEC_REV  : std_logic_vector(3 downto 0) := x"0"
    );
    port(

        -- Control signals
        clk : in std_logic;
        -- This module should not be reset by anything other than a through a
        -- control regiser within the VSEC.

        -- PCIe Extended Capability Interface signals
        read_received : in std_logic;
        write_received : in std_logic;
        register_number : in std_logic_vector(9 downto 0);
        function_number : in std_logic_vector(7 downto 0); -- @suppress "Unused port: function_number is not used in work.vsec_null(Behavioral)"
        write_data : in std_logic_vector(31 downto 0);
        write_byte_enable : in std_logic_vector(3 downto 0);
        read_data : out std_logic_vector(31 downto 0) := x"00000000";
        read_data_valid : out std_logic := '0';

        -- IO to and from the user design.
        status_reg_in : in std_logic_vector(31 downto 0);
        control_reg_out : out std_logic_vector(31 downto 0)
    );
end vsec_null;

architecture Behavioral of vsec_null is





    -- Register map for this PCIe extended capability
    -- PCIe extended capability addresses are given as the word offset from the
    -- EXT_CONFIG_BASE_ADDRESS (not byte offset)
    --               <Register name>     <register offset>                   <offest from base address>
    constant  PCIE_EXT_CAP_ADDR : std_logic_vector(9 downto 0) := "0100100000"; --EXT_CONFIG_BASE_ADDRESS(11 downto 2) + 0;  -- 0x00
    constant  PCIE_VSEC_ADDR    : std_logic_vector(9 downto 0) := "0100100001"; --EXT_CONFIG_BASE_ADDRESS(11 downto 2) + 1;  -- 0x04
    constant  VSEC_STATUS_ADDR  : std_logic_vector(9 downto 0) := "0100100010"; --EXT_CONFIG_BASE_ADDRESS(11 downto 2) + 2;  -- 0x08
    constant  VSEC_CONTROL_ADDR : std_logic_vector(9 downto 0) := "0100100011"; --EXT_CONFIG_BASE_ADDRESS(11 downto 2) + 3;  -- 0x0C

    -- fields for the PCIE_EXT_CAP_ADDR register. A PCIe VSEC is specified with an
    -- PCIE_EXP_CAP_ID=16'h000B and PCIE_EXT_CAP_VER=4'h1 as per the PCIe specification
    constant PCIE_EXP_CAP_ID  : std_logic_vector(15 downto 0) := x"000B";
    constant PCIE_EXT_CAP_VER : std_logic_vector( 3 downto 0) := x"1";

    -- Register/Wire declarations
    -- This header field and values are defined by the PCIe specification for a VSEC
    constant pcie_ext_cap_header : std_logic_vector(31 downto 0) := EXT_CONFIG_NEXT_CAP & PCIE_EXT_CAP_VER & PCIE_EXP_CAP_ID;
    -- This header field is defined by the PCIe specification, but values are defined by
    -- the vendor.
    constant pcie_vsec_header  : std_logic_vector(31 downto 0) := EXT_CONFIG_CAP_LENGTH & PCIE_VSEC_REV & PCIE_VSEC_ID;
    -- This register will be read-only and can be used to report system status. For this
    -- case this register reports the value present in the control register.
    signal pcie_status_reg : std_logic_vector(31 downto 0) := x"00000000";
    -- This register is read-write and can be used for controlling the system or status
    -- register in this case.
    signal pcie_control_reg : std_logic_vector(31 downto 0) := x"04030201";

    signal read_en, write_en: std_logic;

begin

    -- Add function_number filtering HERE if desired.
    -- Currently this is implemented for all functions.
    -- Assign the input and output signals
    read_en <= read_received;
    write_en <= write_received;
    control_reg_out <= pcie_control_reg;
    -- register the data on the status register input.
    process (clk)
    begin
        if rising_edge(clk) then
            pcie_status_reg <= status_reg_in;
        end if;
    end process;

    -- Register Read logic and output registers.
    process (clk)
    begin
        if rising_edge(clk) then
            if (read_en = '1') then
                case (register_number) is
                    when PCIE_EXT_CAP_ADDR =>
                        read_data <= pcie_ext_cap_header;
                        read_data_valid <= '1';
                    when PCIE_VSEC_ADDR =>
                        read_data <= pcie_vsec_header;
                        read_data_valid <= '1';
                    when VSEC_STATUS_ADDR =>
                        read_data <= pcie_status_reg;
                        read_data_valid <= '1';
                    when VSEC_CONTROL_ADDR =>
                        read_data <= pcie_control_reg;
                        read_data_valid <= '1';
                    when others =>
                        read_data <= x"00000000";
                        read_data_valid <= '0';
                end case;
            else
                read_data <= x"00000000";
                read_data_valid <= '0';
            end if;
        end if;
    end process;

    -- Register Write logic
    process (clk)
    begin
        if rising_edge(clk) then
            if (write_en = '1') then
                case (register_number) is
                    -- PCIE_EXT_CAP_ADDR is not writable
                    -- PCIE_VSEC_ADDR is not writable
                    -- VSEC_STATUS_ADDR is not writable
                    when VSEC_CONTROL_ADDR =>
                        for i in 0 to 3 loop
                            if write_byte_enable(i) = '1' then
                                pcie_control_reg(i*8+7 downto i*8) <= write_data(i*8+7 downto i*8);
                            else
                                pcie_control_reg(i*8+7 downto i*8) <= pcie_control_reg(i*8+7 downto i*8);
                            end if;
                        end loop;
                    when others =>
                        pcie_control_reg <= pcie_control_reg;
                end case;
            else
                pcie_control_reg <= pcie_control_reg;
            end if;
        end if;
    end process;

end Behavioral;
